/**
 * @file   Structure.cpp
 * @author Djuro Drljaca (djurodrljaca@gmail.com)
 * @date   2014-04-20
 * @brief  DON data type object: Structure
 *
 * Copyright 2014  Djuro Drljaca (djurodrljaca@gmail.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <DON/Core/DataType/Structure.h>
#include <DON/Core/DataType/UInt8.h>
#include <QtCore/QByteArray>
#include <QtCore/QList>

using namespace DON::Core::DataType;

class Structure::Private
{
public:
    Private()
        : memberList(QList<AbstractDataType *>())
    { }

    ~Private()
    {
        removeAllMembers();
    }

    bool isIndexInRange(const int index) const;

    void removeAllMembers();

    QList<AbstractDataType *> memberList;
};

Structure::Structure()
    : AbstractDataType(),
      d(new Private())
{
}

Structure::Structure(const Structure &other)
    : AbstractDataType(),
      d(new Private())
{
    d->memberList = other.d->memberList;
}

Structure::~Structure()
{
    delete d;
}

Structure & Structure::operator=(const Structure &other)
{
    d->memberList = other.d->memberList;

    return *this;
}

AbstractDataType * Structure::clone() const
{
    return new Structure(*this);
}

DataType Structure::getDataType() const
{
    return DataType_Structure;
}

int Structure::getMemberCount() const
{
    return d->memberList.size();
}

const AbstractDataType * Structure::getMember(const int index) const
{
    const AbstractDataType *member = NULL;

    if (d->isIndexInRange(index))
    {
        member = d->memberList.at(index);
    }

    return member;
}

bool Structure::setMember(const AbstractDataType &member, const int index)
{
    bool success = false;

    if (d->isIndexInRange(index))
    {
        delete d->memberList[index];
        d->memberList[index] = member.clone();
        success = true;
    }

    return success;
}

bool Structure::addMember(const AbstractDataType &member)
{
    bool success = false;

    if (d->memberList.size() < (1 << 8))
    {
        d->memberList.append(member.clone());
        success = true;
    }

    return success;
}

bool Structure::removeMember(const int index)
{
    bool success = false;

    if (d->isIndexInRange(index))
    {
        delete d->memberList.takeAt(index);
        success = true;
    }

    return success;
}

void Structure::removeAllMembers()
{
    d->removeAllMembers();
}

bool Structure::fromBinary(const QByteArray &data, const int startIndex, int *size)
{
    bool success = false;
    QList<AbstractDataType *> memberList;
    int processedDataSize = 0;

    // Parse the structure, minimum of 1 bytes is expected
    if ((data.size() > 0) &&
        (startIndex >= 0) &&
        (startIndex < data.size()))
    {
        // Member Count
        const int memberCount = static_cast<int>(UInt8::fromBinary(data.at(startIndex)));

        // Add member count to the processed data size
        processedDataSize = 1;

        // Member List
        // Calculate start index of the first member
        int memberDataIndex = startIndex + 1;

        // Parse all members
        for (int memberIndex = 0; memberIndex < memberCount; ++memberIndex)
        {
            // Member's data type
            DataType memberDataType = DataType_Invalid;

            if (memberDataIndex < data.size())
            {
                memberDataType = DataType::fromBinaryToDataType(data.at(memberDataIndex));

                if (memberDataType != DataType_Invalid)
                {
                    success = true;

                    // Calculate start index of the member data
                    memberDataIndex += 1;

                    // Add the member data type to the processed data size
                    processedDataSize += 1;
                }
            }

            // Member's data
            if (success)
            {
                // Create an instance of a member with the member's data type
                AbstractDataType *member = AbstractDataType::createInstance(memberDataType);

                if (member != NULL)
                {
                    // Parse member
                    int size = 0;
                    success = member->fromBinary(data, memberDataIndex, &size);

                    if (success)
                    {
                        // Add member to the structure
                        memberList.append(member);
                        member = NULL;

                        // Calculate start index of the next member
                        memberDataIndex += size;

                        // Add the member size to the processed data size
                        processedDataSize += size;
                    }
                    else
                    {
                        // In case of an error delete the member
                        delete member;
                        member = NULL;
                    }
                }
                else
                {
                    // Error, NULL pointer
                    success = false;
                }
            }

            if (success == false)
            {
                // Exit loop in case of an error
                break;
            }
        }
    }

    if (success)
    {
        d->memberList = memberList;

        if (size != NULL)
        {
            *size = processedDataSize;
        }
    }

    return success;
}

QByteArray Structure::toBinary() const
{
    QByteArray data;

    // Member Count
    const int size = d->memberList.size();

    data.append(UInt8::toBinary(static_cast<char>(size)));

    // Member List
    bool success = true;

    for (int memberIndex = 0; memberIndex < size; ++memberIndex)
    {
        // Member
        const AbstractDataType *member = d->memberList.at(memberIndex);

        // Member's data type
        if (member != NULL)
        {
            data.append(DataType::fromDataTypeToBinary(member->getDataType()));
        }
        else
        {
            success = false;
        }

        // Member's data
        if (success)
        {
            const QByteArray memberData = member->toBinary();

            if (memberData.isEmpty() == false)
            {
                data.append(memberData);
            }
            else
            {
                success = false;
            }
        }

        if (success == false)
        {
            data.clear();
            break;
        }
    }

    return data;
}

Structure * Structure::fromAbstractDataType(AbstractDataType *instance)
{
    Structure *outputInstance = NULL;

    if (instance != NULL)
    {
        if (instance->getDataType() == DataType_Structure)
        {
            outputInstance = static_cast<Structure *>(instance);
        }
    }

    return outputInstance;
}

bool Structure::Private::isIndexInRange(const int index) const
{
    bool inRange = false;

    if ((index >= 0) &&
        (index < this->memberList.size()))
    {
        inRange = true;
    }

    return inRange;
}

void Structure::Private::removeAllMembers()
{
    const int size = memberList.size();

    if (size > 0)
    {
        for (int index = 0; index < size; ++index)
        {
            delete memberList[index];
        }

        memberList.clear();
    }
}
